#include <crt_util.h>
#include <ctype.h>

int _valid_hostaddr(const char* addr, int gripe)
{
    if (*addr == 0) {
        return 0;
    }
    if (strchr(addr, ':') != 0)
        return _valid_ipv6_hostaddr(addr, gripe);
    else
        return _valid_ipv4_hostaddr(addr, gripe);
}

int _valid_ipv4_hostaddr(const char* addr_in, int gripe)
{
    char addr[128] = {0x00};
    char* ptr = NULL;
    const char* cp;
    int in_byte = 0;
    int byte_count = 0;
    int byte_val = 0;
    int ch;

#define BYTES_NEEDED 4

    s_strcpy(addr, 128, addr_in);

    if ((ptr = strrchr(addr, '|')) != NULL) {
        *ptr = 0;
    } else if ((ptr = strrchr(addr, ':')) != NULL) {
        *ptr = 0;
    }

    /*
     * Scary code to avoid sscanf() overflow nasties.
     *
     * This routine is called by valid_ipv6_hostaddr(). It must not call that
     * routine, to avoid deadly recursion.
     */
    for (cp = addr; (ch = *(unsigned const char*)cp) != 0; cp++) {
        int id = isdigit(ch);

        if (id) {
            if (in_byte == 0) {
                in_byte = 1;
                byte_val = 0;
                byte_count++;
            }
            byte_val *= 10;
            byte_val += ch - '0';
            if (byte_val > 255) {
                return 0;
            }
        } else if (ch == '.') {
            if (in_byte == 0 || cp[1] == 0) {
                return 0;
            }
            /* XXX Allow 0.0.0.0 but not 0.1.2.3 */
            if (byte_count == 1 && byte_val == 0 && addr[strspn(addr, "0.")]) {
                return 0;
            }
            in_byte = 0;
        } else {
            return 0;
        }
    }

    if (byte_count != BYTES_NEEDED && strcmp(addr, "0") != 0) {
        return 0;
    }

    return 1;
}

int _valid_ipv6_hostaddr(const char* addr_in, int gripe)
{
    int null_field = 0;
    int field = 0;
    char addr[128] = {0x00};
    char* ptr = NULL;

    const unsigned char* cp = (const unsigned char*)addr;
    int len = 0;

    s_strcpy(addr, 128, addr_in);
    if ((ptr = strrchr(addr, '|')) != NULL) {
        *ptr = 0;
    }

    if ((ptr = strrchr(addr, '%')) != NULL) {
        *ptr = 0;
    }

    cp = (const unsigned char*)addr;

    /*
     * FIX 200501 The IPv6 patch validated syntax with getaddrinfo(), but I
     * am not confident that everyone's system library routines are robust
     * enough, like buffer overflow free. Remember, the valid_hostmumble()
     * routines are meant to protect Postfix against malformed information
     * in data received from the network.
     *
     * We require eight-field hex addresses of the form 0:1:2:3:4:5:6:7,
     * 0:1:2:3:4:5:6a.6b.7c.7d, or some :: compressed version of the same.
     *
     * Note: the character position is advanced inside the loop. I have added
     * comments to show why we can't get stuck.
     */
    for (;;) {
        switch (*cp) {
        case 0:
            /* Terminate the loop. */
            if (field < 2) {
                return 0;
            } else if (len == 0 && null_field != field - 1) {
                return 0;
            } else
                return 1;
        case '.':
            /* Terminate the loop. */
            if (field < 2 || field > 6) {
                return 0;
            }
            /* NOT: valid_hostaddr(). Avoid recursion. */
            return _valid_ipv4_hostaddr((const char*)cp - len, gripe);
        case ':':
            /* advance by exactly 1 character position or terminate. */
            if (field == 0 && len == 0 && isalnum((int)cp[1])) {
                return 0;
            }
            field++;
            if (field > 7) {
                return 0;
            }
            cp++;
            len = 0;
            if (*cp == ':') {
                if (null_field > 0) {
                    return 0;
                }
                null_field = field;
            }
            break;
        default:
            /* Advance by at least 1 character position or terminate. */
            len = (int)strspn((const char*)cp, "0123456789abcdefABCDEF");
            if (len /* - strspn((char *) cp, "0") */ > 4) {
                return 0;
            }
            if (len <= 0) {
                return 0;
            }
            cp += len;
            break;
        }
    }
}

int _is_ipv4(const char* ip)
{
    return _valid_ipv4_hostaddr(ip, 0);
}

int _is_ipv6(const char* ip)
{
    return _valid_ipv6_hostaddr(ip, 0);
}

int _is_ip(const char* ip)
{
    return _is_ipv4(ip) || _is_ipv6(ip);
}

int _ipv4_addr_valid(const char* addr)
{
    const char* ptr = addr;
    int n, k;

    if (addr == NULL || *addr == 0)
        return (0);
    k = 3;
    while (*ptr && *ptr != '.') {
        n = *ptr;
        if (n < '0' || n > '9')
            return (0);
        ptr++;
        k--;
        if (k < 0)
            return (0);
    }
    if (*ptr == 0)
        return (0);

    k = 3;
    ptr++;
    while (*ptr && *ptr != '.') {
        n = *ptr;
        if (n < '0' || n > '9')
            return (0);
        ptr++;
        k--;
        if (k < 0)
            return (0);
    }
    if (*ptr == 0)
        return (0);

    k = 3;
    ptr++;
    while (*ptr && *ptr != '.') {
        n = *ptr;
        if (n < '0' || n > '9')
            return (0);
        ptr++;
        k--;
        if (k < 0)
            return (0);
    }
    if (*ptr == 0)
        return (0);

    k = 3;
    ptr++;
    while (*ptr && *ptr != ':') {
        n = *ptr;
        if (n < '0' || n > '9')
            return (0);
        ptr++;
        k--;
        if (k < 0)
            return (0);
    }
    if (*ptr == 0)
        return (0);

    ptr++;
    n = atoi(ptr);
    if (n < 0 || n > 65535)
        return (0);
    return (1);
}

#define IN6ADDRSZ 16
#define INADDRSZ  4
#define INT16SZ   2
int inet_pton4(const char* src, unsigned char* dst)
{
    const char digits[] = "0123456789";
    int saw_digit, octets, ch;
    unsigned char tmp[INADDRSZ], *tp;

    saw_digit = 0;
    octets = 0;
    tp = tmp;
    *tp = 0;
    while ((ch = *src++) != '\0') {
        const char* pch;

        pch = strchr(digits, ch);
        if (pch) {
            unsigned int val = *tp * 10 + (unsigned int)(pch - digits);

            if (saw_digit && *tp == 0)
                return (0);
            if (val > 255)
                return (0);
            *tp = (unsigned char)val;
            if (!saw_digit) {
                if (++octets > 4)
                    return (0);
                saw_digit = 1;
            }
        } else if (ch == '.' && saw_digit) {
            if (octets == 4)
                return (0);
            *++tp = 0;
            saw_digit = 0;
        } else
            return (0);
    }
    if (octets < 4)
        return (0);
    memcpy(dst, tmp, INADDRSZ);
    return (1);
}

int inet_pton6(const char* src, unsigned char* dst)
{
    const char xdigits_l[] = "0123456789abcdef", xdigits_u[] = "0123456789ABCDEF";
    unsigned char tmp[IN6ADDRSZ], *tp, *endp, *colonp;
    const char *xdigits, *curtok;
    int ch, saw_xdigit;
    size_t val;

    memset((tp = tmp), 0, IN6ADDRSZ);
    endp = tp + IN6ADDRSZ;
    colonp = NULL;
    /* Leading :: requires some special handling. */
    if (*src == ':')
        if (*++src != ':')
            return (0);
    curtok = src;
    saw_xdigit = 0;
    val = 0;
    while ((ch = *src++) != '\0') {
        const char* pch;

        pch = strchr((xdigits = xdigits_l), ch);
        if (!pch)
            pch = strchr((xdigits = xdigits_u), ch);
        if (pch != NULL) {
            val <<= 4;
            val |= (pch - xdigits);
            if (++saw_xdigit > 4)
                return (0);
            continue;
        }
        if (ch == ':') {
            curtok = src;
            if (!saw_xdigit) {
                if (colonp)
                    return (0);
                colonp = tp;
                continue;
            }
            if (tp + INT16SZ > endp)
                return (0);
            *tp++ = (unsigned char)((val >> 8) & 0xff);
            *tp++ = (unsigned char)(val & 0xff);
            saw_xdigit = 0;
            val = 0;
            continue;
        }
        if (ch == '.' && ((tp + INADDRSZ) <= endp) && inet_pton4(curtok, tp) > 0) {
            tp += INADDRSZ;
            saw_xdigit = 0;
            break; /* '\0' was seen by inet_pton4(). */
        }
        return (0);
    }
    if (saw_xdigit) {
        if (tp + INT16SZ > endp)
            return (0);
        *tp++ = (unsigned char)((val >> 8) & 0xff);
        *tp++ = (unsigned char)(val & 0xff);
    }
    if (colonp != NULL) {
        /*
         * Since some memmove()'s erroneously fail to handle
         * overlapping regions, we'll do the shift by hand.
         */
        const ssize_t n = tp - colonp;
        ssize_t i;

        if (tp == endp)
            return (0);
        for (i = 1; i <= n; i++) {
            *(endp - i) = *(colonp + n - i);
            *(colonp + n - i) = 0;
        }
        tp = endp;
    }
    if (tp != endp)
        return (0);
    memcpy(dst, tmp, IN6ADDRSZ);
    return (1);
}

int _inet_pton(int af, const char* src, void* dst)
{
    switch (af) {
    case AF_INET:
        return (inet_pton4(src, (unsigned char*)dst));
    case AF_INET6:
        return (inet_pton6(src, (unsigned char*)dst));
    default:
        break;
    }
    return -1;
}

char* inet_ntop4(const unsigned char* src, char* dst, size_t size)
{
    int alen = s_strlen("255.255.255.255");

    char tmp[sizeof("255.255.255.255")];
    size_t len;

    tmp[0] = '\0';

#if (TARGET_OS == OS_UNIX)

    snprintf(
        tmp, alen, "%d.%d.%d.%d", ((int)((unsigned char)src[0])) & 0xff, ((int)((unsigned char)src[1])) & 0xff, ((int)((unsigned char)src[2])) & 0xff,
        ((int)((unsigned char)src[3])) & 0xff);
#else
    sprintf(
        tmp, "%d.%d.%d.%d", ((int)((unsigned char)src[0])) & 0xff, ((int)((unsigned char)src[1])) & 0xff, ((int)((unsigned char)src[2])) & 0xff,
        ((int)((unsigned char)src[3])) & 0xff);
#endif
    // a[0] = ((inaddr & 0xff000000) >> 24);
    // a[1] = ((inaddr & 0x00ff0000) >> 16);
    // a[2] = ((inaddr & 0x0000ff00) >> 8);
    // a[3] = ((inaddr & 0x000000ff) >> 0);

    len = strlen(tmp);
    if (len == 0 || len >= size) {
        errno = ENOSPC;
        return (NULL);
    }
    s_strcpy(dst, size, tmp);
    return dst;
}

char* inet_ntop6(const unsigned char* src, char* dst, size_t size)
{
    int max_size = sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255");

    char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
    char* tp;
    struct {
        long base;
        long len;
    } best, cur;
    unsigned long words[IN6ADDRSZ / INT16SZ];
    int i;

    /* Preprocess:
     *  Copy the input (bytewise) array into a wordwise array.
     *  Find the longest run of 0x00's in src[] for :: shorthanding.
     */
    memset(words, '\0', sizeof(words));
    for (i = 0; i < IN6ADDRSZ; i++)
        words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3));

    best.base = -1;
    cur.base = -1;
    best.len = 0;
    cur.len = 0;

    for (i = 0; i < (IN6ADDRSZ / INT16SZ); i++) {
        if (words[i] == 0) {
            if (cur.base == -1)
                (void)(cur.base = i), cur.len = 1;
            else
                cur.len++;
        } else if (cur.base != -1) {
            if (best.base == -1 || cur.len > best.len)
                best = cur;
            cur.base = -1;
        }
    }
    if ((cur.base != -1) && (best.base == -1 || cur.len > best.len))
        best = cur;
    if (best.base != -1 && best.len < 2)
        best.base = -1;
    /* Format the result. */
    tp = tmp;
    for (i = 0; i < (IN6ADDRSZ / INT16SZ); i++) {
        /* Are we inside the best run of 0x00's? */
        if (best.base != -1 && i >= best.base && i < (best.base + best.len)) {
            if (i == best.base)
                *tp++ = ':';
            continue;
        }

        /* Are we following an initial run of 0x00s or any real hex?
         */
        if (i != 0)
            *tp++ = ':';

        /* Is this address an encapsulated IPv4?
         */
        if (i == 6 && best.base == 0 && (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) {
            if (!inet_ntop4(src + 12, tp, sizeof(tmp) - (tp - tmp))) {
                errno = ENOSPC;
                return (NULL);
            }
            tp += strlen(tp);
            break;
        }
        tp += s_snprintf(tp, max_size, "%lx", words[i]);
    }

    /* Was it a trailing run of 0x00's?
     */
    if (best.base != -1 && (best.base + best.len) == (IN6ADDRSZ / INT16SZ))
        *tp++ = ':';
    *tp++ = '\0';

    /* Check for overflow, copy, and we're done.
     */
    if ((size_t)(tp - tmp) > size) {
        errno = ENOSPC;
        return (NULL);
    }
    s_strcpy(dst, size, tmp);
    return dst;
}

char* _inet_ntop(int af, const void* src, char* buf, size_t size)
{
    switch (af) {
    case AF_INET:
        return inet_ntop4((const unsigned char*)src, buf, size);
    case AF_INET6:
        return inet_ntop6((const unsigned char*)src, buf, size);
    default:
        break;
    }
    return NULL;
}
unsigned int _inet_addr_v4(const char* ip)
{
    unsigned int a = 0;
    unsigned int b = 0;
    unsigned int c = 0;
    unsigned int d = 0;
    unsigned int address = 0;
    sscanf(ip, "%u.%u.%u.%u", &a, &b, &c, &d);

    address |= d << 24;
    address |= c << 16;
    address |= b << 8;
    address |= a;
    return address;
}
